package top.yinjinbiao.demo.netty.custom.spdpa;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import top.yinjinbiao.demo.netty.custom.spdpa.dto.DeviceInfo;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * @Description sample 启动器
 * @Author yin.jinbiao
 * @Date 2021/11/5 11:00
 * @Version 1.0
 */
@Component
@Slf4j
public class NettyBootsrapRunner implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {

    /**
     * 广播服务设备信息键值-每分钟
     */
    public static final String DEVICE_INFO_CURRENT_STATUS_KEY= "RADIO:DEVICEINFO_CURRENT_STATUS:";
    /**
     * 广播服务设备信息键值
     */
    public static final String DEVICE_INFO_LOG_KEY= "RADIO:DEVICEINFO_LOG:";

    @Autowired
    private SpdpaProperties properties;

    private ApplicationContext applicationContext;

    private InetSocketAddress remoteAddress;

    private Channel serverChannel;

    @Autowired
    private RedisTemplate redisTemplate;

    private Map<String, List<DeviceInfo>> map = new ConcurrentHashMap();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        remoteAddress = new InetSocketAddress(properties.getReceiveIp(), properties.getReceivePort());
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //1.NioEventLoopGroup是执行者，也是线程池，线程池中初始化两个线程
        NioEventLoopGroup group = new NioEventLoopGroup(2);
        //2.启动器
        Bootstrap bootstrap = new Bootstrap();
        //3.配置启动器
        bootstrap.group(group)                         //3.1指定group
                .channel(NioDatagramChannel.class)     //3.2指定channel
                .remoteAddress(remoteAddress)          //3.3指定目的地址
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {
                        //3.4在pipeline中加入编码器
                        nioDatagramChannel.pipeline().addLast(new SpdpaUdpEncoder()).addLast(new SpdpaUdpDecoder());
                    }
                });
        try {
            //4.bind并返回一个channel
            Channel channel = bootstrap.bind(properties.getSendPort()).sync().channel();

            this.serverChannel = channel;

            //从线程池中取一个线程，每隔两秒发送一个报文
            ScheduledFuture future = group.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    //5.发送数据
                    String command = "AA5561000000000041646D696E";
                    channel.writeAndFlush(command);
                }
            }, 2, 30, TimeUnit.SECONDS);

            //6.等待channel的close
            channel.closeFuture().sync();
            //7.关闭group
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        if (this.serverChannel != null) {
            this.serverChannel.close();
        }
        log.info("广播netty服务停止");
    }

    /**
     * 编码器，将要发送的消息封装到一个DatagramPacket中
     */
    private class SpdpaUdpEncoder extends MessageToMessageEncoder<String> {
        @Override
        protected void encode(ChannelHandlerContext channelHandlerContext, String msg, List<Object> list) throws Exception {
            ByteBuf byteBuf = Unpooled.copiedBuffer(Hex2ByteUtils.hexToByteArray(msg));
            DatagramPacket packet = new DatagramPacket(byteBuf, remoteAddress);
            list.add(packet);
        }
    }

    /**
     * 解码器
     */
    private class SpdpaUdpDecoder extends MessageToMessageDecoder<DatagramPacket> {
        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket, List<Object> list) throws Exception {
            ByteBuf buffer = datagramPacket.content();
            //取标记位
            short sFlag = buffer.readShort();//AA55
            //取功能号
            byte bFuncCode = buffer.readByte();//61

            //参数1  分区或终端  00 表示终端
            byte bParam1 = buffer.readByte();//00
            //参数2  第几包
            byte bParam2 = buffer.readByte();//01
            //参数3  1表示此包是最后包 0表示还有后续包
            byte bParam3 = buffer.readByte(); //01 最后包
            //参数4  此包终端数量
            byte bParam4 = buffer.readByte();//0F 终端个数为15
            byte bParam5 = buffer.readByte();
            List<DeviceInfo> deviceInfos;
            if (bParam2 == 1) {
                deviceInfos = new ArrayList();
                map.put("deviceInfo", deviceInfos);
            }
            deviceInfos = map.get("deviceInfo");
            //附带数据
            for (int i = 0; i < bParam4; i++) {
                short sDeviceID = buffer.readShort(); //终端ID
                byte bVol = buffer.readByte(); //音量
                short sPartID = buffer.readShort(); //分区ID
                byte bDeviceStateTotal = buffer.readByte();
                byte bDeviceState = (byte) ((bDeviceStateTotal >> 7) & 0x00000001); //终端状态
                byte bDeviceTaskType = (byte) (bDeviceStateTotal & 0x7F);
                //byte bDeviceTaskType = (byte)(bDeviceStateTotal & 0x01111111);
                byte bTaskNum = buffer.readByte(); //任务数
                int nDeviceIP = buffer.readInt(); //IP
                byte bDeviceNameLenth = buffer.readByte(); //终端名称长度
                byte bMusicNameLenth = buffer.readByte(); //终端音源长度
                String devName = "";
                if (bDeviceNameLenth > 0) {
                    byte[] devNameArrayT = new byte[bDeviceNameLenth];
                    buffer.readBytes(devNameArrayT);
                    try {
                        devName = new String(devNameArrayT, "GBK").trim();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                String musicName = "";
                if (bMusicNameLenth > 0) {
                    byte[] devMusicArray = new byte[bMusicNameLenth];
                    buffer.readBytes(devMusicArray);
                    try {
                        musicName = new String(devMusicArray, "GBK").trim();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                //取出尾部 * 符号
                buffer.readByte();
                DeviceInfo info = new DeviceInfo();
                info.setDeviceID(sDeviceID);
                info.setDeviceIP(ipIntToString(nDeviceIP));
                info.setDeviceVol(bVol);
                info.setDeviceName(devName);
                info.setMusicName(musicName);
                info.setTaskNum(bTaskNum);
                info.setDeviceState(bDeviceState);
                info.setBDeviceTaskType(bDeviceTaskType);
                info.setTime(LocalDateTime.now());
                DateTimeFormatter df = DateTimeFormatter.ofPattern("HH");
                String hour = df.format(LocalDateTime.now());
                info.setHour(hour);
                df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                String date = df.format(LocalDateTime.now());
                info.setDateTime(date);
                deviceInfos.add(info);
            }
            // 最后包
            if (bParam3 == 1) {
                // 拿出缓存
                List<DeviceInfo> cacheDeviceInfoList = map.get("deviceInfo");
                redisTemplate.opsForValue().set(DEVICE_INFO_CURRENT_STATUS_KEY + properties.getReceiveIp(), cacheDeviceInfoList, 1, TimeUnit.DAYS);
            }
        }
    }

    public static String ipIntToString(int nIP) {
        int ip1, ip2, ip3, ip4;
        ip1 = (nIP >> 24) & 0xFF;
        ip2 = (nIP >> 16) & 0xFF;
        ip3 = (nIP >> 8) & 0xFF;
        ip4 = (nIP) & 0xFF;
        return ip4 + "." + ip3 + "." + ip2 + "." + ip1;
    }
}
